/**
 * 所有UI根节点的父类
 * 
 * 生命周期
 * 
 * 特殊注意：在一个page里面，如果要show另一个page
 * 1、beforeShow返回true了，这种情况请确保在onShow被执行之前，不要show其它page
 * 2、beforeShow返回false，这种情况，后续的流程都不会触发了，beforeShow函数里可以show其它page
 * 
 * 例如：
 * pageIndex.js
 * beforeShow(data,next){ next(true) };
 * onLoad(){
 *      app.manager.ui.show({name:'pageGame'}); // 错误
 * }
 * onShow(){
 *      app.manager.ui.show({name:'pageGame'}); // 正确
 * }
 */

/**
 * show流程: [beforeShow] -> [show(业务层不能重写)] -> [dataModel] -> [onLoad(ccc引擎调度)] -> [onEnable(ccc引擎调度)] -> 
 * [其它page的hide流程(page_show_page时)] -> [其它UI的onLostFocus] -> [onFocus] -> [onShow] -> [start]
 * 
 * hide流程: [beforeHide] -> [hide(业务层不能重写)] -> [onHide] -> [onDisEnable(ccc引擎调度)] -> [onDestroy(ccc引擎调度)] -> [onLostFocus] -> [其它UI的onFocus]
 */

/**
 * 小知识
 * 1、关于ctor和properties的触发顺序？
 *  properties中的属性会被优先定义，然后在执行ctor，如果脚本挂在到编辑器中，执行完前两步之后，编辑器的属性面板才会去读取数值
 * 
 * 2、ccc原有生命周期顺序(包括cc.Director.EVENT_BEFORE_UPDATE、cc.Director.EVENT_AFTER_UPDATE、schedule方法和touch事件)
 * 
 *  2.1、当前帧执行了onLoad：
 *  当前帧: beforeUpdate -> onLoad -> onEnable(与onDisable一样是根据active立即触发的) -> afterUpdate(当前帧并不触发update、schedule、lateUpdate，从下一帧开始)
 *  下一帧: beforeUpdate -> start -> update -> schedule -> lateUpdate -> afterUpdate
 *  之后帧: beforeUpdate -> update -> schedule -> lateUpdate -> afterUpdate
 * 
 *  2.2、当前帧没有执行onLoad：
 *  beforeUpdate -> update -> lateUpdate -> afterUpdate
 * 
 *  2.3、touch事件更要优于beforeUpdate，但onLoad帧会不会触发不得而知
 * 
 *  结论：
 *  (1) touch事件是最先被触发的
 *  (2) cc.Director.EVENT_BEFORE_UPDATE优于所有生命周期，cc.Director.EVENT_AFTER_UPDATE相反
 *  (3) onLoad这一帧并不触发update、schedule、lateUpdate
 *  (4) schedule总是在当前帧(onLoad帧除外)的update之后、lateUpdate之前触发(this.scheduleOnce(function () {})就是在当前帧触发)
 */

const dotReWriteFuns = ['resetInEditor', 'node_stopPropagation', 'parent_stopPropagation', 'setTouchEnabled', 'onBtnClick', 'show', 'hide', 'focus', 'refresh'];
const dotCallFuns = ['show', 'refresh', 'focus'];

const BlockEvents = [
    'touchstart', 'touchmove', 'touchend', 'touchcancel',
    'mousedown', 'mousemove', 'mouseup',
    'mouseenter', 'mouseleave', 'mousewheel'
];

const HideEvent = cc.Enum({
    active: 1,
    destroy: 2,
    postion: 3
})

const { ccclass, property } = cc._decorator;
import { app } from "../app";
import { viewKeys } from "../executor";

@ccclass
export default abstract class baseView extends cc.Component {
    // 当前是否处于展示中
    private _showing = false;
    // 当前view的名字
    private _viewName: keyof typeof viewKeys = cc.js.getClassName(this) as any;

    @property({ type: HideEvent, tooltip: '何种方式隐藏节点' })
    protected hideEvent = HideEvent.active;

    @property({ tooltip: '是否捕获焦点<响应onLostFocus和onFocus>\n注意: 当一个捕获焦点的view处于最上层并展示时\n下层的view永远不会响应focus事件' })
    protected captureFocus = true;

    @property({ tooltip: '是否需要底层遮罩' })
    protected shade = true;

    @property
    private _singleton = true;
    private static _singleton = true;
    @property({ tooltip: '是否是单例模式(非单例模式下view会被重复创建)', visible(this: baseView) { return !!this._viewName && !this._viewName.startsWith('page'); } })
    protected get singleton(): boolean {
        return this._singleton && (<typeof baseView>this.constructor)._singleton;
    };
    protected set singleton(value) {
        this._singleton = (<typeof baseView>this.constructor)._singleton = !!value;
    };

    // 将view与自身融合(被融合的view不能是page), 一般用于page融合paper
    protected mixinViews: (keyof typeof viewKeys)[] = [];

    constructor() {
        super();

        if (CC_EDITOR) {
            dotReWriteFuns.forEach((funName) => {
                if (baseView.prototype[funName] !== this[funName]) {
                    Editor.warn(`[${this._viewName}] [warn] 不应该重写父类方法{${funName}}`);
                }
            });
        }

        if (!CC_EDITOR && CC_DEBUG) {
            dotCallFuns.forEach((funName) => {
                this[funName] = (function (className, funName) {
                    return function () {
                        throw new Error(`[${className}] [error] 请不要手动调用{${funName}}方法, 这是由框架调用的`)
                    }
                })(this._viewName, funName);
            })
        }
    }

    // 用来初始化组件或节点的一些属性，当该组件被第一次添加到节点上或用户点击了它的 Reset 菜单时调用。这个回调只会在编辑器下调用。
    protected resetInEditor(): any {
        if (CC_EDITOR) {
            const widget = this.node.getComponent(cc.Widget) || this.node.addComponent(cc.Widget);
            widget.isAlignBottom = true;
            widget.isAlignLeft = true;
            widget.isAlignRight = true;
            widget.isAlignTop = true;
            widget.top = 0;
            widget.left = 0;
            widget.right = 0;
            widget.bottom = 0;
            widget.alignMode = cc.Widget.AlignMode.ON_WINDOW_RESIZE;

            if (!this.node.getComponent(cc.BlockInputEvents)) {
                this.node.addComponent(cc.BlockInputEvents);
            }
        }
    }

    private node_stopPropagation(event: cc.Event): any {
        if (!event.target.forceTouch) {
            cc.log(`[${this._viewName}] [log] 触摸屏蔽`);
            event.stopPropagation();
        }
    }

    private parent_stopPropagation(event: cc.Event): any {
        if (event.target === this.node && !(<any>(this.node)).forceTouch) {
            cc.log(`[${this._viewName}] [log] 触摸屏蔽`);
            event.stopPropagation();
        }
    }

    /**
     * 在机制触摸时，设置节点的forceTouch属性为true，可强制开启触摸
     * @param {*} enabled 
     */
    protected setTouchEnabled(enabled: boolean): any {
        if (!enabled) {
            for (let i = 0; i < BlockEvents.length; i++) {
                this.node.on(BlockEvents[i], this.node_stopPropagation, this, true);
                this.node.parent.on(BlockEvents[i], this.parent_stopPropagation, this, true);
            }
        } else {
            for (let i = 0; i < BlockEvents.length; i++) {
                this.node.off(BlockEvents[i], this.node_stopPropagation, this, true);
                this.node.parent.off(BlockEvents[i], this.parent_stopPropagation, this, true);
            }
        }
    }

    public get viewName(): string {
        return this._viewName;
    }

    public get isSingleton(): boolean {
        return this.singleton || this._viewName.startsWith('page');
    }

    public get isCaptureFocus(): boolean {
        return this.captureFocus;
    }

    public get isNeedShade(): boolean {
        return this.shade;
    }

    public get isShowing(): boolean {
        return this._showing;
    }

    private show(data?: any, onShow?: Function, onHide?: Function, beforeShow?: Function): this {
        const next = (res: boolean) => {
            if (res) {
                this._showing = true;

                onHide && this.node.once('onHide', onHide);

                this.dataModel();

                if (this.node.active !== true) { this.node.active = true; }
                if (this.hideEvent === HideEvent.postion) { this.node.x = 0; this.node.opacity = 255; }
                app.manager.ui.refreshShade();
            }
            beforeShow && beforeShow(res);
            if (res) {
                const result = this.onShow(data);
                onShow && onShow(result);

                this.node.emit('onShow', result);
                app.manager.ui.emit(`${this._viewName}`, { event: 'onShow', result: result });
                app.manager.ui.emit('onShow', { name: `${this._viewName}`, result: result });
            }
        }

        if (this.mixinViews.length) {
            const task = app.lib.task.createASync();
            this.mixinViews.forEach(name => {
                if (name.startsWith('page')) {
                    this.error(`不能融合${name}, 已跳过`);
                } else {
                    task.add((next, retry) => {
                        app.manager.ui.preload(name, result => {
                            result ? next() : this.scheduleOnce(retry, 0.1);
                        })
                    })
                }
            })
            task.start(() => this.beforeShow(data, res => {
                next(res);
                if (res) {
                    this.mixinViews.forEach(name => app.manager.ui.show({ name, data }))
                }
            }));
        } else {
            this.beforeShow(data, next);
        }

        return this;
    }

    protected hide(data?: any, onHide?: Function, beforeHide?: Function): this {
        const next = (res: boolean) => {
            beforeHide && beforeHide(res);
            if (res) {
                this._showing = false;

                const result = this.onHide(data);
                onHide && onHide(result);

                this.node.emit('onHide', result);
                app.manager.ui.emit(`${this._viewName}`, { event: 'onHide', result: result });
                app.manager.ui.emit('onHide', { name: `${this._viewName}`, result: result });

                if (this.hideEvent === HideEvent.active) { this.node.active = false }
                else if (this.hideEvent === HideEvent.destroy) { app.manager.ui.release(this); }
                else if (this.hideEvent === HideEvent.postion) { this.node.x = 10000; this.node.opacity = 0; }
                app.manager.ui.refreshShade();
            }
        }
        if (this.mixinViews.length) {
            this.beforeHide(data, res => {
                if (res) {
                    this.mixinViews.forEach(name => app.manager.ui.hide({ name, data }));
                }
                next(res);
            });
        } else {
            this.beforeHide(data, next);
        }
        return this;
    }

    private refresh(data?: any, onRefresh?: Function, beforeRefresh?: Function): this {
        const next = (res: boolean) => {
            beforeRefresh && beforeRefresh(res);
            if (res) {
                const result = this.onRefresh(data);
                onRefresh && onRefresh(result);

                this.node.emit('onRefresh', result);
                app.manager.ui.emit(`${this._viewName}`, { event: 'onRefresh', result: result });
                app.manager.ui.emit('onRefresh', { name: `${this._viewName}`, result: result });
            }
        }

        if (this.mixinViews.length) {
            this.beforeRefresh(data, res => {
                next(res);
                if (res) {
                    this.mixinViews.forEach(name => app.manager.ui.refresh({ name, data }));
                }
            });
        } else {
            this.beforeRefresh(data, next);
        }
        return this;
    }

    private focus(boo: boolean): any {
        let result = null;
        let event = '';
        if (boo) {
            result = this.onFocus();
            event = 'onFocus';
        } else {
            result = this.onLostFocus();
            event = 'onLostFocus';
        }

        this.node.emit(event, result);
        app.manager.ui.emit(`${this._viewName}`, { event: event, result: result });
        app.manager.ui.emit(event, { name: `${this._viewName}`, result: result });
    }

    protected log(str, ...args) {
        console.log(`[${this._viewName}] [log] ${str}`, ...args);
    }
    protected warn(str, ...args) {
        console.warn(`[${this._viewName}] [warn] ${str}`, ...args);
    }
    protected error(str, ...args) {
        console.error(`[${this._viewName}] [error] ${str}`, ...args);
    }

    // 以下为可重写

    /**
     * dataModel方法在beforeShow之后onShow之前会自动调用
     */
    protected dataModel(): any {

    }

    protected onShow(data?: any): any {
        return data;
    }

    protected onHide(data?: any): any {
        return data;
    }

    protected onRefresh(data?: any): any {
        return data;
    }

    protected onLostFocus(): any {
        return true;
    }

    protected onFocus(): any {
        return true;
    }

    protected beforeShow(data: any, next: (result: boolean) => void): any {
        next(true);
    }

    protected beforeHide(data: any, next: (result: boolean) => void): any {
        next(true);
    }

    protected beforeRefresh(data: any, next: (result: boolean) => void): any {
        next(true);
    }
}